package com.dmagic.cms.common.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <h3>Class name</h3> <h4>Description</h4> <h4>Special Notes</h4>
 * 
 * @version 1.0
 * @author songlin.li 2012-5-29
 */
public class ClassUtil extends ClassUtils {
    protected transient static final Log logger = LogFactory.getLog(ClassUtil.class);

    /**
     * @description : copy a not null property value to another bean
     * @param dest 
     * @param orig
     */
    public static void copyProperties(Object dest, Object orig) {
        Field[] fields = orig.getClass().getDeclaredFields();

        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true);
            String filedName = field.getName();

            try {
                Object retobj = getFieldValue(orig, filedName);
                if (retobj == null) {
                    continue;
                } else {
                    setFieldValue(dest, filedName, retobj);
                }
            } catch (Exception e) {
                logger.error("", e);
            }
        }
    }

    /**
     * @description : Call class methods, including private
     * @param object access object
     * @param methodName Designated member variable name
     * @param type parameter type
     * @param value parameter
     * @return Object Returns the results object
     */
    public static Object useMethod(Object object, String methodName, Class<?>[] type, Object... value) {
        Class<?> classType = object.getClass();
        Method method = null;
        Object fildValue = null;

        try {
            method = classType.getDeclaredMethod(methodName, type);
            // Set up safety inspection, access to private members method must
            // be
            method.setAccessible(true);
            fildValue = method.invoke(object, value);

        } catch (NoSuchMethodException e) {
            logger.error("", e);
        } catch (Exception e) {
            logger.error("", e);
        }

        return fildValue;
    }

    /**
     * @description : Direct get variable value
     * @param object
     * @param filedName
     * @return
     * @author Jay.Wu
     */
    public static Object getFieldValue(Object object, String filedName) {
        Object fildValue = null;

        try {
            fildValue = BeanUtils.getProperty(object, filedName);
        } catch (IllegalAccessException e) {
            logger.error(e.getMessage(), e);
        } catch (InvocationTargetException e) {
            logger.error(e.getMessage(), e);
        } catch (NoSuchMethodException e) {
            logger.warn(e.getMessage());
        }

        return fildValue;
    }

    /**
     * Direct set variable assignment
     * 
     * @param obj
     * @param filedName
     * @param fieldValue
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @author Jay.Wu
     */
    /**
     * @description :
     * @param obj
     * @param filedName
     * @param fieldValue
     * @throws Exception
     * @author Jay.Wu
     */
    public static void setFieldValue(Object obj, String filedName, Object fieldValue) throws Exception {
        Field field = getField(obj, filedName);
        Class<?>[] types = new Class<?>[] { Integer.class, Long.class, Double.class };

        try {
            if (StringUtil.isNotBlank(fieldValue)) {
                if (!field.getType().getName().equals(fieldValue.getClass().getName())) {
                    for (int i = 0; i < types.length; i++) {
                        if (field.getType().getName().equals(types[i].getName())) {
                            fieldValue = newObjectByConstructor(field.getType(), new Class<?>[] { String.class },
                                    new Object[] { fieldValue.toString() });
                            break;
                        }
                    }
                }
                // Set up safety inspection, access to private member variables
                // must be
                field.setAccessible(true);
                field.set(obj, fieldValue);
                // BeanUtils.copyProperty(dest, filedName, fieldValue);
            }
        } catch (Exception e) {
            logger.error("", e);
        }
    }

    public static Field getField(Object obj, String filedName) {
        Field field = null;
        List<Class<?>> list = new ArrayList<Class<?>>();
        getClasses(obj, list);

        for (Class<?> classObj : list) {
            try {
                field = classObj.getDeclaredField(filedName);
                break;
            } catch (SecurityException e) {
                logger.error("", e);
            } catch (NoSuchFieldException e) {
            }
        }

        return field;
    }

    private static void getClasses(Object obj, List<Class<?>> list) {
        Class<?> classObj = null;

        if (obj instanceof Class<?>) {
            classObj = ((Class<?>) obj).getSuperclass();

            if (list.size() == 0) {
                list.add((Class<?>) obj);
            }
        } else {
            classObj = obj.getClass().getSuperclass();

            if (list.size() == 0) {
                list.add(obj.getClass());
            }
        }

        list.add(classObj);

        if (!classObj.getName().equals(Object.class.getName())) {
            getClasses(classObj, list);
        }
    }

    /**
     * Through the constructor obtain examples
     * 
     * @param className Kind of fully qualified name
     * @param intArgsClass The constructor argument types
     * @param intArgs The constructor parameter value
     * @return Object
     */
    public static Object newObjectByConstructor(Class<?> classObj, Class<?>[] intArgsClass, Object[] intArgs) {
        Object returnObj = null;
        try {
            // Find the specified structure method
            Constructor<?> constructor = classObj.getDeclaredConstructor(intArgsClass);
            // Set up safety inspection, access to private constructor must
            constructor.setAccessible(true);
            returnObj = constructor.newInstance(intArgs);
        } catch (Exception e) {
            logger.error("", e);
            e.printStackTrace();
        }

        return returnObj;
    }

    public static Map<String, Field> getFields(Object object) {
        Map<String, Field> map = new HashMap<String, Field>();

        List<Class<?>> list = new ArrayList<Class<?>>();
        getClasses(object, list);

        for (int i = list.size() - 1; i > -1; i--) {
            Field[] fields = list.get(i).getDeclaredFields();
            for (int j = 0; j < fields.length; j++) {
                map.put(fields[j].getName(), fields[j]);
            }
        }

        return map;
    }

}
